/*esp8266网页控制小车，网页采用html和javascript开发*/
/*
负责接收esp8266传过来的数据
并且生成，前端显示数据，
可以通过前端控制小车，和stm32，
控制stm32的模式。

主代码采用轮训的模式触发，IO扫描来反馈循迹模块
用一个主函数来简单初始化

前端其实可以不用创建这么多的按钮，可以多个按钮返回不同的按键值，
对应不同的模式，进行轮寻

协议

今天目标改造串口接受判断

框图思路，先实现stm32 接收ip地址先
arduino 用到的函数原型

默认模式，发现警报停车。
可以增加一个模式，强行巡逻，当发现警报时候继续行驶。

发现问题，应该是切换频率太高

*/
#include "car.h"
#include <ESP8266WiFi.h>       // 本程序使用 ESP8266WiFi库
#include <ESP8266WebServer.h>  //  ESP8266WebServer库
#include <Ticker.h>  //定时器库
#include <PubSubClient.h>
#include <string.h>
#include "html.h"

#define mqttPort 1883
ESP8266WebServer esp8266_server(80);  // 建立网络服务器对象，该对象用于响应HTTP请求。监听端口（80）
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);  //创建mqtt对象
//设置定时器1，函数，服务器连接函数，和定时保持心跳
Ticker Ticker_Client;

//wifi账号密码
const char* ssid = "fish";
const char* password = "12345679";
const char* mqttServer = "test.ranye-iot.net";

/*放置全局变量*/
u8 flagSpeed = 1;    //选择小车速度的变量
u8 openTracing = 0;  //控制循迹模式开关的变量
u8 flagFlame;
u8 flagToxi;
u8 flagFound;
u8 flagBeep;
u8 flagcompare;

/*串口字符串存放和识别*/
/*串口发送端
"# + '内容选择一个字节'+数据#"
例子 "#a23"
a温度b湿度c火焰d有毒e人体f IP地址
STM32接受端
@+内容+#     @和#为开始帧和结束帧s
*/
char rxdata[20];
char tempdata[10];
char humidata[10];
u8 compare;

int foundnum = 0;

//IP地址转化String 函数
String IpAddress2String(const IPAddress& ipAddress) {
  return String(ipAddress[0]) + String(".") + String(ipAddress[1]) + String(".") + String(ipAddress[2]) + String(".") + String(ipAddress[3]);
}

//----------------------------------------------------------------------------------------------------
void setup(void) {
  Serial.begin(115200);  // 启动  串口通讯
  WiFi.mode(WIFI_STA);   //设置wifi模式
  connectWifi();
  carInit();
  serverInit();
  pwmInit();
  mqttInit();
  Serial.println("HTTP esp8266_server started");  //  告知用户ESP8266网络服务功能已经启动,可以改为发送给stm32

  u8 pwmCtrl = 0;
}

void loop(void) {
  uart_rx();
  rx_judge();
  esp8266_server.handleClient();  // 检查http服务器访问
  if(openTracing==1){
  tracing();
  }
    
}
/*服务器初始化*/
void serverInit() {
  esp8266_server.begin();              // 启动网站服务
  esp8266_server.on("/", handleRoot);  // 设置服务器根目录即'/'的函数'handleRoot'
  esp8266_server.on("/UP", handleForward);
  esp8266_server.on("/BACK", handleBackward);
  esp8266_server.on("/STOP", handleStop);
  esp8266_server.on("/SPEED", handleSpeed);
  esp8266_server.on("/LEFT", handleLeft);
  esp8266_server.on("/RIGHT", handleRight);
  esp8266_server.on("/TRACING", handleTracing);
  esp8266_server.on("/TEMP", handleTemp);
  esp8266_server.on("/HUMI", handleHumi);
  esp8266_server.on("/FLAME", handleFlame);
  esp8266_server.on("/TOXI", handleToxi);
  esp8266_server.on("/FOUND", handleFound);
  esp8266_server.on("/NUM",handleNum);
  esp8266_server.on("/BEEP", handleBeep);
  esp8266_server.on("/NIGHT", handleNight);

  esp8266_server.onNotFound(handleNotFound);  // 设置处理404情况的函数'handleNotFound'
}

void handleRoot() {
  esp8266_server.send(200, "text/html", myhtml);
}

void handleForward() {
  forward();
  mqttState("UP");
}
void handleBackward() {
  backward();
   mqttState("BLACK");
}
void handleStop() {
  stopcar();
  flagSpeed = 0;
  openTracing = 0;
  Serial.print(openTracing);
   mqttState("STOP");
}
void handleSpeed() {
  if (flagSpeed = 3) { flagSpeed = 0; }
  flagSpeed++;
  analogWrite(PWM1, flagSpeed * 200);  //720ok 620ok 550ok 400ok 200ok
  analogWrite(PWM2, flagSpeed * 200);  //720ok 620ok 550ok 400ok 200ok
}
void handleLeft() {
  leftcar();
   mqttState("LEFT");
}
void handleRight() {
  rightcar();
   mqttState("RIGHT");
}

/*循迹开关按钮*/
void handleTracing() {
  if(openTracing == 0 ){
    openTracing = 1;
    Serial.print("AUTOON");
    mqttState("AUTOON");  
  }else{
    openTracing = 0;  
    Serial.print("AUTOOFF");
     mqttState("AUTOOFF");}
}
//前端温度发送
void handleTemp() {
  esp8266_server.send(200, "/TEMP", tempdata);
}
void handleHumi() {
  esp8266_server.send(200, "/HUMI", humidata);  //前端湿度发送
}
void handleFlame() {
  if (flagFlame == 1) {
    esp8266_server.send(200, "/FLAME", "HIGH");
    flagFlame = 0;
  } else
    esp8266_server.send(200, "/FLAME", "LOW");
}
void handleToxi() {
    if (flagToxi == 1) {
    esp8266_server.send(200,"/TOXI", "HIGH");
    flagToxi = 0;
  } else
    esp8266_server.send(200,"/TOXI", "LOW");
}
void handleFound() {
      if (flagFound == 1) {
    esp8266_server.send(200, "/FOUND", "HIGH");
    flagFound = 0;
  } else
    esp8266_server.send(200, "/FOUND", "LOW");
}
void handleNum(){
  char a[10];
  itoa(foundnum,a,10);//整形转字符串
  esp8266_server.send(200, "/FOUND",a);
}
void handleBeep() {
  Serial.write("@BEEP");//
  Serial.write("#\n");
   mqttState("BEEP");
}
void handleNight() {
  Serial.write("@NIGHT");//
  Serial.write("#\n");
  mqttState("NIGHT");
}

// 设置处理404情况的函数'handleNotFound'
void handleNotFound() {
  esp8266_server.send(404, "text/plain", "404");
}
/*串口接收数据转换成char,字符串比较标志位置一*/
void uart_rx() {
  //如果串口有消息才接受反应
  String data;
  if (Serial.available() > 0)
    data = Serial.readStringUntil('#');
  strcpy(rxdata, data.c_str());
  flagcompare = 1;
}
/*字符串扫描判断
字符串比较标志位置一次比较一次*/
void rx_judge() {
  u8 i;
  if (flagcompare == 1) {
    compare = rxdata[0];
    switch (compare) {
      case 'a':
        {
          for (i = 0; i < 2; i++)
          tempdata[i] = rxdata[i + 1];
          mqttTemp(tempdata);
          Serial.println(tempdata);
        }
        break;
      case 'b':
        {
          for (i = 0; i < 2; i++)
            humidata[i] = rxdata[i + 1];
            mqttHumi(humidata);
          Serial.println(humidata);
        }
        break;
      case 'c':
        {
          flagFlame = 1;  //火焰传感器
          stopcar();          
          mqttFlame(1);
        }
        break;
      case 'd':
        {
          flagToxi = 1;  //有毒气体
          stopcar();
          mqttToxi(1);
        }
        break;
      case 'e':
        {
          flagFound = 1;  //红外人体
          mqttFound(1);
          foundnum++;
        }
        break;
      case 'f':
        {
          Serial.write("@IP");
          Serial.write(IpAddress2String(WiFi.localIP()).c_str());  //发送IP地址
          Serial.write("#\n");
        }
        break;
    }
  }
  flagcompare = 0;
}
/*------------------------------------------MQTT-----------------------------------------------------------*/
void mqttInit(void){
    // 设置MQTT服务器和端口号
  mqttClient.setServer(mqttServer, mqttPort);
  mqttClient.setCallback(receiveCallback);   //设置订阅的回调函数

  // 连接MQTT服务器
  connectMQTTServer();
  // Ticker定时对象
  Ticker_Client.attach(3, Tim_Client);

    mqttFlame(flagFlame);
    mqttToxi(flagToxi);
    mqttFound(flagFound);
    mqttConnect();
  
}

/*
  describe:火焰传感器发布主题 0发布off 1发布on
  parma：flame(0)   flame(1)
*/
void mqttFlame(int x)
{
  // 建立flame主题并推送消息
  String flameString = "esp8266/flame";
  char publishflame[flameString.length() + 1];
  strcpy(publishflame, flameString.c_str());

  String messageString;
  if (x == 1)
    messageString = "HIGH";
  else
    messageString = "LOW";
  char publishMsg[messageString.length() + 1];
  strcpy(publishMsg, messageString.c_str());
  // 实现ESP8266向主题发布信息
  mqttClient.publish(publishflame, publishMsg);
}
/*
   describe:温度发布主题
   parma:要发送的数据
*/
void mqttTemp(char * data)
{
  // 建立temp主题并推送消息
  String tempString = "esp8266/temp";
  char publishtemp[tempString.length() + 1];
  strcpy(publishtemp, tempString.c_str());

  String messageString;
  messageString = data;

  char publishMsg[messageString.length() + 1];
  strcpy(publishMsg, messageString.c_str());

  // 实现ESP8266向主题发布信息
  mqttClient.publish(publishtemp, publishMsg);
}
/*
   describe:湿度发布主题
   parma:要发送的数据
*/
void mqttHumi(char * data)
{
  // 建立humi主题并推送消息
  String DHT11String = "esp8266/humi";
  char publishDHT11[DHT11String.length() + 1];
  strcpy(publishDHT11, DHT11String.c_str());

  String messageString;
  messageString = data;

  char publishMsg[messageString.length() + 1];
  strcpy(publishMsg, messageString.c_str());

  // 实现ESP8266向主题发布信息
  mqttClient.publish(publishDHT11, publishMsg);
}

void mqttToxi(int x)
{
    // 建立Toxi主题并推送消息
  String foundString = "esp8266/toxi";
  char publishfound[foundString.length() + 1];
  strcpy(publishfound, foundString.c_str());

  String messageString;
  if (x == 1)
    messageString = "HIGH";
  else
    messageString = "LOW";
  char publishMsg[messageString.length() + 1];
  strcpy(publishMsg, messageString.c_str());
  // 实现ESP8266向主题发布信息
  mqttClient.publish(publishfound, publishMsg);
}
/*
  describe:HR传感器发布主题 0发布off 1发布on
  parma：flame(0)   flame(1)
*/
void mqttFound(int x)
{
  // 建立found主题并推送消息
  String foundString = "esp8266/found";
  char publishfound[foundString.length() + 1];
  strcpy(publishfound, foundString.c_str());

  String messageString;
  if (x == 1)
    messageString = "HIGH";
  else
    messageString = "LOW";
  char publishMsg[messageString.length() + 1];
  strcpy(publishMsg, messageString.c_str());
  // 实现ESP8266向主题发布信息
  mqttClient.publish(publishfound, publishMsg);
}

void mqttConnect()
{
  // 建立found主题并推送消息
  String foundString = "esp8266/connect";
  char publishfound[foundString.length() + 1];
  strcpy(publishfound, foundString.c_str());

  String messageString;
    messageString = "OK";
  char publishMsg[messageString.length() + 1];
  strcpy(publishMsg, messageString.c_str());
  // 实现ESP8266向主题发布信息
  mqttClient.publish(publishfound, publishMsg);
}

void mqttState(String order )
{
  // 建立found主题并推送消息
  String foundString = "esp8266/state";
  char publishfound[foundString.length() + 1];
  strcpy(publishfound, foundString.c_str());

  String messageString;
    messageString = order;
  char publishMsg[messageString.length() + 1];
  strcpy(publishMsg, messageString.c_str());
  // 实现ESP8266向主题发布信息
  mqttClient.publish(publishfound, publishMsg);
}


/*
  Ticker ticker;// 建立Ticker用于实现定时功能
  保持MQTT连接，定时心跳
*/
void Tim_Client() {
  if (mqttClient.connected()) { // 如果开发板成功连接服务器
    // 保持心跳
    mqttClient.loop();
    mqttTX();
  }
  else {                  // 如果开发板未能成功连接服务器
    connectMQTTServer();    // 则尝试连接服务器
    Serial.print("MQTT Server Connect Failed. Client State:");
    Serial.println(mqttClient.state());
  }
}
/*定时发送MQTT检测，用来刷新*/
void mqttTX(){     
    mqttFlame(flagFlame);
    mqttToxi(flagToxi);
    mqttFound(flagFound);
    mqttHumi(humidata);
    mqttTemp(tempdata);
    mqttConnect();
}


/*下面是MQTT的函数---------下面不用改动------------------------------------------*/
void connectMQTTServer() {
  // 根据ESP8266的MAC地址生成客户端ID（避免与其它ESP8266的客户端ID重名）
  String clientId = "esp8266-" + WiFi.macAddress();

  // 连接MQTT服务器
  if (mqttClient.connect(clientId.c_str())) {
    Serial.println("MQTT Server Connected.");
    Serial.println("Server Address: ");
    Serial.println(mqttServer);
    subscribeTopic(); // 订阅指定主题
  } else {
    Serial.print("MQTT Server Connect Failed. Client State:");
    Serial.println(mqttClient.state());
  }
}

// 订阅指定主题
void subscribeTopic() {
  // 这么做是为确保不同设备使用同一个MQTT服务器测试消息订阅时，所订阅的主题名称不同
  String topicString = "esp8266/control";
  char subTopic[topicString.length() + 1];
  strcpy(subTopic, topicString.c_str());

  // 通过串口监视器输出是否成功订阅主题以及订阅的主题名称
  if (mqttClient.subscribe(subTopic)) {
    Serial.println("Subscribe Topic:");
    Serial.println(subTopic);
  } else {
    Serial.print("Subscribe Fail...");
  }

}

/* 收到信息后的回调函数
    topic 回调函数订阅的主题
    payload 回调函数收到信息的字节
    length 回调函数收到信息的长度

可以接收MQTT客户端发送过来的数据
*/
void receiveCallback(char* topic, byte* payload, unsigned int length) {
  //将订阅的控制主题收到的信息通过串口打印出来
  Serial.println("receive messmage");
  int i;
  for (i = 0; i < length; i++)
    Serial.print((char)payload[i]);
}

/*--------------------------------------------------------------------------------------------------------*/
void connectWifi() {
  WiFi.begin(ssid, password);
  //等待WiFi连接,成功连接后输出成功信息
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
    Serial.write("@IP");
    Serial.write("NO,wifi");  //发送IP地址
    Serial.write("#\n");
  }
  Serial.println("WiFi Connected!");
  // WiFi连接成功后将通过串口监视器输出连接成功信息
  Serial.println(WiFi.localIP());  // 通过串口监视器输出ESP8266-NodeMCU的IP
  Serial.write("@IP");
  Serial.write(IpAddress2String(WiFi.localIP()).c_str());  //发送IP地址
  Serial.write("#\n");
}
